Implications of Substitution עזאם מרעי המחלקה למדעי המחשב אוניברסיטת בן-גוריון
2 Roadmap In this chapter we will investigate some of the implications of the principle of substitution in statically typed object-oriented programming languages. In particular, we will consider: The impact on memory management The meaning of assignment The distinction between testing for identity and testing for equality
3 Idealization of is-a Relationship A TextWindow is-a Window Because TextWindow is subclassed from Window, All behavior associated with Windows is also manifest by instances of TextWindow Therefore, a variable declared as maintaining an instance of Window should be able to hold a value of type TextWindow Unfortunately, practical programming language implementation issues complicate this idealized picture
4 Memory Allocation Stack and Heap Based Generally, programming languages use two different techniques for allocation of memory: Stack-based allocation Amount of space required is determined at compile time, based on static types of variables Memory allocation and release is tied to procedure entry/exit Can be performed very efficiently Heap-based allocation Amount of space used can be determined at run-time, based upon dynamic considerations Memory allocation and release is not tied to procedure entry/exit, and either must be handled by user or by a run-time library (garbage collection) Generally considered to be somewhat less efficient
5 Memory Allocation Stack and Heap Based Stack Heap pointer Object pointer
6 The Problem with Substitution class Window { public: virtual void oops(); private: int height; int width; }; class TextWindow : public Window { public: virtual void oops(); private: char contents[1000]; int cursorlocation; }; Window x; // how much space to set aside? TextWindow y; x = y; // what should happen here?
7 Memory Strategies How much memory should be set aside for the variable x? Minimum Static Space Allocation Allocate the amount of space necessary for the base class only (C++) Maximum Static Space Allocation Allocate the amount of space for the largest subclass Dynamic Space Allocation Allocate for x only the amount of space required to hold a pointer (Smalltalk, Java)
8 Minimum Static Space Allocation The language C++ uses the minimum static space allocation approach This is very efficient, but leads to some subtle difficulties What happens in the following assignment? Window x; TextWindow y; x = y;
9 Assigning a Larger Value to a Smaller Box x height width y height Width Content cursorlocation
10 The Slicing Problem The problem is you are trying to take a large box and squeeze it into a small space. Clearly this won't work. Thus, the extra fields are simply sliced off A B
11 The Slicing Problem The problem is you are trying to take a large box and squeeze it into a small space. Clearly this won't work. Thus, the extra fields are simply sliced off A B Question - Does this matter? Answer - Only if somebody notices Solution - Design the language to make it difficult to notice
12 Method Invocations void Window::oops() {cout<<"window oops\n ;} void TextWindow::oops() { cout<<"textwindow oops\n"<<cursorlocation;} TextWindow x; Window a; Window *b; TextWindow *c; a = x; a.oops();
13 Rules for Member Function Binding in C++ The rules for deciding what member function to execute are complicated because of the slicing problem: With variables that are declared normally The binding of member function name to function body is based on the static type of the argument (regardless whether the function is virtual or not) With variables that are declared as references or pointers The binding of the member function name to function body is based on the dynamic type if the function is declared as virtual, and the static type if not
14 Illustration void Window::oops() { printf("window oops\n"); } void TextWindow::oops() { printf("textwindow oops %d\n", cursorlocation); } TextWindow x; Window a; Window *b; TextWindow *c; a = x; a.oops(); b = &x; b->oops(); c = &x; c->oops();
15 Illustration void Window::oops() { printf("window oops\n"); } void TextWindow::oops() { printf("textwindow oops %d\n", cursorlocation); } TextWindow x; Window a; Window *b; TextWindow *c; a = x; a.oops(); b = &x; b->oops(); c = &x; c->oops(); // executes Window version
16 Illustration void Window::oops() { printf("window oops\n"); } void TextWindow::oops() { printf("textwindow oops %d\n", cursorlocation); } TextWindow x; Window a; Window *b; TextWindow *c; a = x; a.oops(); // executes Window version b = &x; b->oops(); // executes TextWindow or Window version c = &x; c->oops();
17 Illustration void Window::oops() { printf("window oops\n"); } void TextWindow::oops() { printf("textwindow oops %d\n", cursorlocation); } TextWindow x; Window a; Window *b; TextWindow *c; a = x; a.oops(); // executes Window version b = &x; b->oops(); // executes TextWindow or Window version c = &x; c->oops(); // executes TextWindow version
18 Dynamic Memory Allocation In the third approach, all objects are actually pointers Only enough space for a pointer is allocated at compile time Actual data storage is allocated on the heap at run-time Used in Smalltalk, Object Pascal, and Objective-C, Java Requires user to explicitly allocate new objects and, in some languages, explicitly free no longer used storage May also lead to pointer semantics for assignment and equality testing
19 Illustration Window win; Window *winptr; Window *txtwinptr = new TextWindow; win = *txtwinprt; winptr = txtwinptr; win.oops(); // executes Window version winptr->oops(); // executes TextWindow or Window version;
20 Meaning of Assignment? What does it mean when an instance of a class is assigned to another variable? class Box { public int value; } Box x = new Box(); x.value = 7; Box y = x; y.value = 12; System.out.println(x.value); Copy semantics: x and y are independent of each other, a change in one has no effect on the other Pointer semantics: x and y refer to the same object, and hence a change in one will alter the other
21 Copy Semantics versus Pointer Semantics If a value is indirectly accessed through a pointer When an assignment is performed (or equality test is made) Is the operation assigns/tests simply the pointer or is it the actual value? x y
22 Problems with Pointer Semantics If x is assigned to y and then changes are made to x, are these changes reflected in y? If x is explicitly freed, what happens if the user tries to access memory through y? Object Pascal, Java uses pointer semantics, no built-in provision for copies Smalltalk and Objective-C use pointer semantics, have several techniques for making copies
23 An Old Joke Concerning Equality There is an old joke that goes something like this: A man walks into a pizza parlor and sits down A waiter comes to the table and asks the man what he would like to order The man looks around the room, then points to the woman sitting at the next table, and says I'll have what she is eating The waiter thereupon walks to the woman's table, picks up the half-eaten pizza from in front of her, and places it before the startled customer A classic confusion between equality and identity
24 Equality and Identity A test for identity asks whether two variables refer to exactly the same object A test for equality asks whether two variables refer to values that are equivalent Of course, the meaning of equivalent is inherently domain specific Object-oriented languages allow the programmer to control the meaning of the equality test by allowing the redefinition of a standard method. (for example, equals in Java)
25 Equality Equality and Identity A variable holds the same instance as another variable a is equal but not identical to b Integer a = new Integer(1); Integer b = new Integer(1); Identity Two (distinct) objects can be used interchangeably. They often have the same id x is identical to y. Integer x = new Integer(1); Integer y = x; Of course, two identical objects are always equal In java, equality is defined by the equals method. Keep in mind, if you implement equals you must also implement hashcode.
26 Identity and Equality x x ABC y ABC y ABC char *a = ABC ; char *b = ABC ; if (a == b) // will,surprisingly, not be true
27 Paradoxes of Equality, Part 1 But child classes cannot change the type signature of overridden methods This means the argument must often be more general than one would like: class Object { public boolean equals (Object right) {... } } class PlayingCard extends Object { public boolean equals (Object right) {... // right must be object even if we are only... // interested in comparing cards to cards } }
28 Paradoxes of Equality, Part 2 Because equality is a message sent to the left argument, There is no guarantee that properties such as symmetry or transitivity are preserved: class Foo { boolean equals (Object right) {... } } Foo a, b; if (a.equals(b)) // even if this is true if (b.equals(a)) // no guarantee that this is true
29 Paradoxes of Equality, Part 3 And if you add inheritance into the mix, the possibilities for paradoxical behavior increase even more: class Parent { boolean equals (Object x) {... } } class Child extends Parent { boolean equals (Object x) {... } } Parent p; Child c; if (p.equals(c)) // will execute using the parent method if (c.equals(p)) // will execute using the child s method
30 Paradoxes of Equality, Part 4 If a class has an equals method and also implements Comparable (for example), Then it is advisable (but not enforced) that exactly when a.equals(b) a.compareto(b) == 0 Odd behavior can result if this is violated
31 Copies and Clones When a language use pointer semantics for assignment, they almost always provide a means to create copy, or clone, from a value But even in this simple task there are subtle issues that can trap the unwary In particular, what actions should be taken when creating a copy of a value that itself points to other objects?
32 Copies and Clones There are two possible interpretations: Shallow copy: Share instance variables with the original. The original and the copy reference the exact same value. Deep copy: Create new copies of the instance values. Recursively!
33 Copies and Clones Shallow Deep y x y x a b c a b c a' b' c'
34 Copy constructors is C++ class Complex { public Complex ( const Complex &source) { rl = source.rl; im = source.im; } }
35 Cloning in Java class PlayingCard implements Clonable { public public Object clone() throws CloneNotSupportedException { Object newcard = super.clone(); return newcard; } } The default behavior creates a shallow copy. If deep copy, or any other domain-specific behavior, is desired, the programmer can add code after invoking parent method
36 Return Type of clone is Object One disadvantage with the design of the clone() method is that the return type of clone() is Object, and needs to be explicitly cast back into the appropriate type. However, overriding clone() to return the appropriate type is preferable and eliminates the need for casting in the client (using covariant return types, that we will discuss in a later chapter). Another disadvantage is that one often cannot access the clone() method on an abstract type. Most interfaces and abstract classes in Java do not specify a public clone() method. As a result, often the only way to use the clone() method is if you know the actual class of an object; which is contrary to the abstraction principle For example, if one has a List reference in Java, one cannot invoke clone() on that reference because List specifies no public clone() method.
37 Chapter Summary We have explored the implications that result from the inclusion of the principle of substitution in an object oriented programming language. Because values are not known until run time, you either have complex semantics (as in C++) or objects are dynamic (as in Java and most other languages). Because objects are dynamic, most object-oriented languages end up using a garbage collection system. Dynamic semantics naturally lean to pointer semantics for assignment Pointer semantics mean that equality and identity are two different concepts Since equality is domain specific, the programmer must be free to redefine the meaning as appropriate Because the programmer can redefine equality arbitrarily, there is no guarantee that semantics of equals is preserved.